package org.third.integration.auth.jaas.authorization;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PushbackInputStream;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

/**
 * <p>
 * This Sample application attempts to authenticate a user and executes a
 * SampleAction as that user.
 *
 * <p>
 * If the user successfully authenticates itself, the username and number of
 * Credentials is displayed.
 */
class IdmJaasConfiguration extends Configuration {

    @Override
    public AppConfigurationEntry[] getAppConfigurationEntry(String name) {

        List<AppConfigurationEntry> appConfigures = new ArrayList<AppConfigurationEntry>();
        // Sample {
        // org.third.auth.jaas.authorization.module.SampleLoginModule required
        // debug=true;
        // };

        String sampleLoginModuleName = "org.third.auth.jaas.authorization.module.SampleLoginModule";
        LoginModuleControlFlag sampleControlFlag = LoginModuleControlFlag.SUFFICIENT;  
        Map<String, Object> samplepOptions = new HashMap<String, Object>();
        samplepOptions.put("debug", Boolean.TRUE);
        AppConfigurationEntry entry = new AppConfigurationEntry(sampleLoginModuleName, sampleControlFlag,
                samplepOptions);
        appConfigures.add(entry);

        String sampleLoginModuleName2 = "org.third.auth.jaas.authorization.module.SampleLoginModule2";
        LoginModuleControlFlag sampleControlFlag2 = LoginModuleControlFlag.OPTIONAL;
        Map<String, Object> samplepOptions2 = new HashMap<String, Object>();
        samplepOptions.put("debug", Boolean.TRUE);
        AppConfigurationEntry entry2 = new AppConfigurationEntry(sampleLoginModuleName2, sampleControlFlag2,
                samplepOptions2);
        appConfigures.add(entry2);

        return appConfigures.toArray(new AppConfigurationEntry[] {});
    }

}

public class SampleAzn {

    /**
     * Attempt to authenticate the user.
     *
     * <p>
     * 
     * -Djava.security.auth.login.config=C:\IDM\help\src\auth\org\third\auth\
     * jaas\authorization\sample_jaas.config -Djava.security.manager
     * -Djava.security.policy=C:\IDM\help\src\auth\org\third\auth\jaas\
     * authorization\sampleazn.policy
     * </p>
     * 
     * @param args
     *            input arguments for this application. These are ignored.
     */

    public static void main(String[] args) {

        // Obtain a LoginContext, needed for authentication. Tell it
        // to use the LoginModule implementation specified by the
        // entry named "Sample" in the JAAS login configuration
        // file and to also use the specified CallbackHandler.

        LoginContext lc = null;
        try {
            javax.security.auth.login.Configuration conf = new IdmJaasConfiguration();
            // lc = new LoginContext("Sample", new MyCallbackHandler());
            lc = new LoginContext("Sample", null, new MyCallbackHandler(), conf);
        } catch (LoginException le) {
            System.err.println("Cannot create LoginContext. " + le.getMessage());
            System.exit(-1);
        } catch (SecurityException se) {
            System.err.println("Cannot create LoginContext. " + se.getMessage());
            System.exit(-1);
        }

        // the user has 3 attempts to authenticate successfully
        int i;
        for (i = 0; i < 3; i++) {
            try {

                // attempt authentication
                lc.login();

                // if we return with no exception, authentication succeeded
                break;

            } catch (LoginException le) {

                System.err.println("Authentication failed:");
                System.err.println("  " + le.getMessage());
                try {
                    Thread.currentThread().sleep(3000);
                } catch (Exception e) {
                    // ignore
                }

            }
        }

        // did they fail three times?
        if (i == 3) {
            System.out.println("Sorry");
            System.exit(-1);
        }

        System.out.println("Authentication succeeded!");

        Subject mySubject = lc.getSubject();

        // let's see what Principals we have
        Iterator principalIterator = mySubject.getPrincipals().iterator();
        System.out.println("Authenticated user has the following Principals:");
        while (principalIterator.hasNext()) {
            Principal p = (Principal) principalIterator.next();
            System.out.println("\t" + p.toString());
        }

        System.out.println("User has " + mySubject.getPublicCredentials().size() + " Public Credential(s)");

        // now try to execute the SampleAction as the authenticated Subject
        PrivilegedAction action = new SampleAction();
        Object obj = Subject.doAsPrivileged(mySubject, action, null);

        System.out.println(obj);

        System.exit(0);
    }
}

/**
 * A CallbackHandler implemented by the application.
 *
 * <p>
 * This application is text-based. Therefore it displays information to the user
 * using the OutputStreams System.out and System.err, and gathers input from the
 * user using the InputStream System.in.
 */
class MyCallbackHandler implements CallbackHandler {

    /**
     * Invoke an array of Callbacks.
     *
     * <p>
     *
     * @param callbacks
     *            an array of <code>Callback</code> objects which contain the
     *            information requested by an underlying security service to be
     *            retrieved or displayed.
     *
     * @exception java.io.IOException
     *                if an input or output error occurs.
     *                <p>
     *
     * @exception UnsupportedCallbackException
     *                if the implementation of this method does not support one
     *                or more of the Callbacks specified in the
     *                <code>callbacks</code> parameter.
     */
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof TextOutputCallback) {

                // display the message according to the specified type
                TextOutputCallback toc = (TextOutputCallback) callbacks[i];
                switch (toc.getMessageType()) {
                case TextOutputCallback.INFORMATION:
                    System.out.println(toc.getMessage());
                    break;
                case TextOutputCallback.ERROR:
                    System.out.println("ERROR: " + toc.getMessage());
                    break;
                case TextOutputCallback.WARNING:
                    System.out.println("WARNING: " + toc.getMessage());
                    break;
                default:
                    throw new IOException("Unsupported message type: " + toc.getMessageType());
                }

            } else if (callbacks[i] instanceof NameCallback) {

                // prompt the user for a username
                NameCallback nc = (NameCallback) callbacks[i];

                System.err.print(nc.getPrompt());
                System.err.flush();
                nc.setName((new BufferedReader(new InputStreamReader(System.in))).readLine());

            } else if (callbacks[i] instanceof PasswordCallback) {

                // prompt the user for sensitive information
                PasswordCallback pc = (PasswordCallback) callbacks[i];
                System.err.print(pc.getPrompt());
                System.err.flush();
                pc.setPassword(readPassword(System.in));

            } else {
                throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
            }
        }
    }

    // Reads user password from given input stream.
    private char[] readPassword(InputStream in) throws IOException {

        char[] lineBuffer;
        char[] buf;
        int i;

        buf = lineBuffer = new char[128];

        int room = buf.length;
        int offset = 0;
        int c;

        loop: while (true) {
            switch (c = in.read()) {
            case -1:
            case '\n':
                break loop;

            case '\r':
                int c2 = in.read();
                if ((c2 != '\n') && (c2 != -1)) {
                    if (!(in instanceof PushbackInputStream)) {
                        in = new PushbackInputStream(in);
                    }
                    ((PushbackInputStream) in).unread(c2);
                } else
                    break loop;

            default:
                if (--room < 0) {
                    buf = new char[offset + 128];
                    room = buf.length - offset - 1;
                    System.arraycopy(lineBuffer, 0, buf, 0, offset);
                    Arrays.fill(lineBuffer, ' ');
                    lineBuffer = buf;
                }
                buf[offset++] = (char) c;
                break;
            }
        }

        if (offset == 0) {
            return null;
        }

        char[] ret = new char[offset];
        System.arraycopy(buf, 0, ret, 0, offset);
        Arrays.fill(buf, ' ');

        return ret;
    }
}